import network.Device;
import network.Network;
import network.Rule;
import networkLoader.FBNetwork;
import networkLoader.FabricNetwork;
import partition.*;
import verifier.JiffyVerifier;
import verifier.Ports.PersistentPorts;
import verifier.util.Changes;

import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
import java.util.*;

public class SubnetPartitionTest {
    public static Network globalNetwork;

    public static void testDifferentSubnetSpace(int nSubnets) {
        Network network = FabricNetwork.getNetwork(112);
        HashMap<Device, ArrayList<Rule>> deviceToRules = new HashMap<>();
        int nRules = 0;
        for (Rule rule : network.getInitialRules()) {
            if (rule.getMatch().longValue() >> 24 < nSubnets) { // test pod2 as subnet
                deviceToRules.computeIfAbsent(rule.getDevice(), k -> new ArrayList<>());
                deviceToRules.get(rule.getDevice()).add(rule);
                nRules++;
            }
        }
        JiffyVerifier verifier = new JiffyVerifier(network, new PersistentPorts());
        long s = System.nanoTime();
        for (ArrayList<Rule> rules : deviceToRules.values()) {
            Changes changes = verifier.insertMiniBatch(rules);
            changes.aggrBDDs();
            verifier.update(changes);
        }
        long t = System.nanoTime() - s;
        System.out.println("#subnets " + nSubnets + " takes: " + t);
        System.out.println("#rules: " + nRules);
        System.out.println("#ECs: " + verifier.predSize());
    }

    public static void testSubnetNetwork() {
//        Network network = FabricNetwork.getNetwork(112);
        Network network = FBNetwork.getNetwork();
        globalNetwork = network;
        HashMap<Device, HashSet<Rule>> deviceToRules = new HashMap<>();
        for (Rule rule : network.getInitialRules()) {
            if (rule.getMatch().longValue() >> 24 == 1) { // test pod2 as subnet
                deviceToRules.computeIfAbsent(rule.getDevice(), k -> new HashSet<>());
                // introduce bug by random forward
//                rule.setRandomOutPort();
                deviceToRules.get(rule.getDevice()).add(rule);
            }
        }
//        System.out.println(deviceToRules.values().stream().map(HashSet::size).reduce(Integer::sum));

        HashMap<String, Partition> nameToPartition = Partitioner.setupPartitionWithLTs(network);

        for (Partition partition : nameToPartition.values()) {
            HashSet<Rule> rulesInPartition = new HashSet<>();
            for (Device device : partition.getNetwork().getAllDevices()) {
                rulesInPartition.addAll(deviceToRules.get(device));
            }
            long s = System.nanoTime();
            partition.runSubnet(100, rulesInPartition);
            System.out.println("model update takes: " + (System.nanoTime() - s));
//            partition.runLocalTasks();
        }
        for (int i =0;i<5;i++) {
//            Aggregator aggregator = new Aggregator(nameToPartition.values());
//            long s = System.nanoTime();
//            aggregator.getNetworkModel();
//            System.out.println("global model takes: " + (System.nanoTime() - s));
        }
//        aggregator.network = network;
//        aggregator.allPairGNM();
//        aggregator.network = globalNetwork;
//        aggregator.allPairWithLTsBackward();
//        List<Device> allDevices = new LinkedList<>(network.getAllDevices());
//        Collections.shuffle(allDevices);
//        Set<Device> closedDevices = new HashSet<>(allDevices.subList(0, 20));
//        closedDevices.forEach(c -> System.out.print(c.getName()));
//        System.out.println();
//        s = System.nanoTime();
//        EarlyDetector.detectLoop(network, aggregator.getNetworkModel(), closedDevices);
//        System.out.println("early takes: " + (System.nanoTime() - s));
    }

    public static HashMap<String, SubnetPartition> setupPartitionWithLTs(boolean withLTs) {
        Network network = FabricNetwork.getNetwork(112);
        globalNetwork = network;
        HashMap<String, SubnetPartition> nameToPartition = FabricNetwork.setupSubnetPartitions(network);
        if (!withLTs) {
            return nameToPartition;
        }
        String[] ptnames = {"pod-0", "pod-1", "spine-0-0"};
        HashSet<String> ptns = new HashSet<>(Arrays.asList(ptnames));
        for (Partition partition : nameToPartition.values()) {
            String ptname = partition.getName();
            if (!ptns.contains(ptname)) {
//                continue;
            }
            if (ptname.startsWith("pod")) {
                String podId = ptname.split("-")[1];
                // inter pod up
                for (int iRsw = 0; iRsw < 48; iRsw++) {
                    for (int iFsw = 0; iFsw < 4; iFsw++) {
                        for (int iPort = 0; iPort < 48; iPort++) {
                            LocalTask localTask = new LocalTask(partition.getNetwork().getDevice("rsw-" + podId + "-" + iRsw),
                                    partition.getNetwork().getDevice("fsw-" + podId + "-" + iFsw).getPort("fsw-" + podId + "-" + iFsw + ">ssw-" + iFsw + "-" + iPort));
                            partition.addLocalTask(localTask);
                        }
                    }
                }
                // inter pod down
                for (int iFsw = 0; iFsw < 4; iFsw++) {
                    for (int iRsw = 0; iRsw < 48; iRsw++) {
                        LocalTask localTaskDown = new LocalTask(
                                partition.getNetwork().getDevice("fsw-" + podId + "-" + iFsw),
                                partition.getNetwork().getDevice("rsw-" + podId + "-" + iRsw).getPort("rsw-" + podId + "-" + iRsw + ">h-" + podId + "-" + iRsw));
                        partition.addLocalTask(localTaskDown);
                    }
                }
                // intra pod
                for (int iRsw = 0; iRsw < 48; iRsw++) {
                    for (int iRsw1 = 0; iRsw1 < 48; iRsw1++) {
                        if (iRsw != iRsw1) {
                            LocalTask localTask = new LocalTask(partition.getNetwork().getDevice("rsw-" + podId + "-" + iRsw),
                                    partition.getNetwork().getDevice("rsw-" + podId + "-" + iRsw1).getPort("rsw-" + podId + "-" + iRsw1 + ">h-" + podId + "-" + iRsw1));
                            partition.addLocalTask(localTask);
                        }
                    }
                }
            }
            if (ptname.startsWith("spine")) {
                for (Device device : partition.getNetwork().getAllDevices()) {
                    String iSpine = ptname.split("-")[1];
                    String iSsw = ptname.split("-")[2];
                    for (int iPod = 0; iPod < 112; iPod++) {
                        if (iPod != Integer.parseInt(iSsw)) {
                            LocalTask localTask = new LocalTask(device,
                                    device.getPort(device.getName() + ">fsw-" + iPod + "-" + iSpine));
                            partition.addLocalTask(localTask);
                        }
                    }
                }
            }

        }
        return nameToPartition;
    }

    public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
        testSubnetNetwork();
//        testDifferentSubnetSpace(1); // for JIT
//        for (int i = 0; i < 112; i++) {
//            testDifferentSubnetSpace(i);
//            System.gc();
//        }
        System.exit(0);
        HashMap<String, SubnetPartition> nameToPartition = setupPartitionWithLTs(true);
        for (SubnetPartition partition : nameToPartition.values()) {
            partition.test(100);
//            partition.runLocalTasks();
        }

//        Aggregator aggregator = new Aggregator(nameToPartition);
//        aggregator.network = globalNetwork;
//        aggregator.allPairWithLTs();

//        long bddTT = 0;
//        long totalTT = 0;
//        long bddopTT = 0;
//        int transferCnt = 0;
//        for (int i = 0; i < 10; i++) {
//            Aggregator aggregator = new Aggregator(nameToPartition);
//            long s = System.nanoTime();
//            aggregator.checkReach();
//            bddTT += aggregator.bddTransferTime;
//            bddopTT += aggregator.bddOpTime;
//            totalTT += (System.nanoTime() - s);
//            transferCnt += aggregator.transferCnt;
//        }
//        System.out.println("total time: " + totalTT / 10000 + "us");
//        System.out.println("bdd transfer time: " + bddTT / 10000 + "us");
//        System.out.println("bdd transfer cnt: " + (transferCnt / 10));
//        System.out.println("bdd op time: " + bddopTT / 10000 + "us");
//        System.out.println("other time: " + (totalTT - bddTT - bddopTT) / 10000 + "us");

//        AbstractNetwork abstractNetwork = new AbstractNetwork(nameToPartition);
//        abstractNetwork.originNet = network;
//        long s = System.nanoTime();
////        abstractNetwork.aggr();
////        System.out.println(System.nanoTime() - s);
//        s = System.nanoTime();
//        abstractNetwork.allPairReach();
//        System.out.println("all pair check takes: " + (System.nanoTime() - s));

    }
}
